package com.electromagnetic.industry.software.manage.service.serviceimpl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.codec.Base64;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.lang.tree.Tree;
import cn.hutool.core.lang.tree.TreeNodeConfig;
import cn.hutool.core.lang.tree.TreeUtil;
import cn.hutool.core.text.StrFormatter;
import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.SecureUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.electromagnetic.industry.software.common.cons.ElectromagneticConstants;
import com.electromagnetic.industry.software.common.enums.EffectFlagEnum;
import com.electromagnetic.industry.software.common.enums.EleDataTypeEnum;
import com.electromagnetic.industry.software.common.exception.BizException;
import com.electromagnetic.industry.software.common.pojo.RespPageVO;
import com.electromagnetic.industry.software.common.util.EleCommonUtil;
import com.electromagnetic.industry.software.common.util.EleIdCreator;
import com.electromagnetic.industry.software.common.util.UserThreadLocal;
import com.electromagnetic.industry.software.manage.config.ElePropertyConfig;
import com.electromagnetic.industry.software.manage.mapper.ImportPrjInfoMapper;
import com.electromagnetic.industry.software.manage.pojo.models.ImportFileInfo;
import com.electromagnetic.industry.software.manage.pojo.req.ImportFileDirQueryVO;
import com.electromagnetic.industry.software.manage.pojo.req.ImportFileInfoVO;
import com.electromagnetic.industry.software.manage.pojo.resp.ImportPrjTreeVO;
import com.electromagnetic.industry.software.manage.service.ImportPrjService;
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.io.FileSystemResource;
import org.springframework.core.io.InputStreamResource;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;

import java.io.File;
import java.io.InputStream;
import java.util.*;
import java.util.stream.Collectors;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;

import static com.electromagnetic.industry.software.common.cons.ElectromagneticConstants.*;

@Slf4j
@Service
public class ImportPrjServiceImpl extends ServiceImpl<ImportPrjInfoMapper, ImportFileInfo> implements ImportPrjService {

    @Resource
    private ElePropertyConfig elePropertyConfig;

    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean importPrj(MultipartFile file) {
        Assert.isTrue(Objects.equals(FileUtil.getSuffix(file.getOriginalFilename()), "zip"), "当前仅支持zip格式");
        String zipTmpPath = elePropertyConfig.getEleTmpPath() + File.pathSeparator + UUID.randomUUID() + ".zip";
        ZipFile zipFile = null;
        try {
            FileUtil.writeFromStream(file.getInputStream(), zipTmpPath);
            Map<String, String> pathIdMap = new HashMap<>();
            List<ImportFileInfo> items = new ArrayList<>();
            zipFile = EleCommonUtil.getZipFile(zipTmpPath);
            Map<String, ImportFileInfo> itemIdMap = new HashMap<>();
            Map<String, ZipEntry> entryMap = new HashMap<>();
            Enumeration<? extends ZipEntry> entries = zipFile.entries();
            // 预处理：建立路径映射
            while (entries.hasMoreElements()) {
                ZipEntry entry = entries.nextElement();
                String entryName = entry.getName();
                // 跳过根目录（如果存在）
                if (entryName.isEmpty()) continue;
                // 解析路径结构
                String[] pathParts = entryName.split("/");
                String currentPath = "";
                String parentId = ElectromagneticConstants.PRJ_PARENT_ID; // 根目录的parentId为0

                for (int i = 0; i < pathParts.length; i++) {
                    String name = pathParts[i];
                    if (name.isEmpty()) continue;
                    currentPath += (i == 0 ? "" : "/") + name;
                    // 如果是目录且不是最后一个元素
                    boolean isDirectory = i < pathParts.length - 1 || entry.isDirectory();
                    if (!pathIdMap.containsKey(currentPath)) {
                        String newId = EleIdCreator.createSnowflakeId();
                        pathIdMap.put(currentPath, newId);
                        String findIdPath = findIdPath(parentId, itemIdMap);
                        String filePath = StrUtil.isEmpty(findIdPath) ? newId : findIdPath + ElectromagneticConstants.MYSQL_FILE_PATH_SPLIT + newId;
                        ImportFileInfo importFileInfo = new ImportFileInfo()
                                .setId(newId)
                                .setParentId(parentId)
                                .setFileType(FileUtil.getSuffix(entryName))
                                .setFileName(FileUtil.mainName(entryName))
                                .setFilePath(filePath)
                                .setFileContent("")
                                .setDataType(isDirectory ? EleDataTypeEnum.FOLDER.code : EleDataTypeEnum.FILE.code)
                                .setFileSize(entry.getSize())
                                .setSrcPath(entryName)
                                .setPermanentDeleted(false);
                        resetPrjName(importFileInfo);
                        if (isDirectory) {
                            importFileInfo.setSort(createFolderSort(parentId, itemIdMap));
                        } else {
                            entryMap.put(newId, entry);
                        }
                        items.add(importFileInfo);
                        itemIdMap.put(newId, importFileInfo);
                        parentId = newId;
                    } else {
                        parentId = pathIdMap.get(currentPath);
                    }
                }
            }
            this.saveBatch(items);
            for (Map.Entry<String, ZipEntry> entry : entryMap.entrySet()) {
                String newId = entry.getKey();
                ZipEntry tmp = entry.getValue();
                InputStream inputStream = zipFile.getInputStream(tmp);
                String destPath = elePropertyConfig.getImportPrjPath() + File.separator + newId;
                FileUtil.writeFromStream(inputStream, destPath);
                EleCommonUtil.encryptFile(destPath, SecureUtil.aes(elePropertyConfig.getFileEncPasswd().getBytes()));
            }
            String prjId = items.stream().filter(e -> e.getParentId().equals(PRJ_PARENT_ID)).findFirst().get().getId();
            String prjPath = elePropertyConfig.getImportPrjPath() + File.separator + prjId;
            FileUtil.writeFromStream(file.getInputStream(), prjPath);
            EleCommonUtil.encryptFile(prjPath, SecureUtil.aes(elePropertyConfig.getFileEncPasswd().getBytes()));
            UserThreadLocal.setSuccessInfo("", prjId, "导入本地工程成功");
        } catch (Exception e) {
            throw new BizException("导入工程失败，原因 " + e.getMessage(), e);
        } finally {
            IoUtil.close(zipFile);
            FileUtil.del(zipTmpPath);
        }
        return true;
    }

    @Override
    public ResponseEntity<InputStreamResource> download(String id, HttpServletResponse response) {
        String fileName = "";
        ImportFileInfo fileInfo = this.baseMapper.selectById(id);
        String fileSysPath = elePropertyConfig.getImportPrjPath() + File.separator + id;
        String dbPath = fileInfo.getSrcPath();
        try {
            Assert.isTrue(FileUtil.exist(fileSysPath), "下载文件不存在，路径为 {}", dbPath);
            FileSystemResource fileSystemResource = new FileSystemResource(fileSysPath);
            fileName = fileSystemResource.getFilename();
            HttpHeaders headers = new HttpHeaders();
            headers.add("Cache-Control", "no-cache, no-store, must-revalidate");
            headers.add("Pragma", "no-cache");
            headers.add("Expires", "0");
            String newFileName = Base64.encode(fileInfo.getFileName() + "." + fileInfo.getFileType());
            response.setHeader("content-disposition", "attachment;filename=" + newFileName);
            UserThreadLocal.setSuccessInfo(fileInfo.getParentId(), id, "下载文件 {} 成功，文件路径 {}", fileInfo.getFileName() + "." + fileInfo.getFileType(), dbPath);
            // 构建响应实体(可以返回<byte[]或Resource，返回类型取决body入参类型)
            return ResponseEntity
                    .ok()
                    .headers(headers)
                    .contentLength(fileSystemResource.contentLength())
                    .contentType(MediaType.parseMediaType("application/octet-stream;charset=UTF-8"))
                    .body(new InputStreamResource(fileSystemResource.getInputStream()));
        } catch (Exception e) {
            String info = StrFormatter.format("下载文件异常 {}，路径 {}", fileName, dbPath);
            log.error(info, e);
            throw new BizException(info);
        }
    }

    @Override
    public RespPageVO<ImportFileDirQueryVO> queryPrjInfo(Integer pageNum, Integer pageSize) {
        Page<ImportFileInfo> importFileInfoPage = this.baseMapper.selectPage(new Page<>(pageNum, pageSize), Wrappers.lambdaQuery(ImportFileInfo.class)
                .select(ImportFileInfo::getId, ImportFileInfo::getFileName)
                .eq(ImportFileInfo::getEffectFlag, EffectFlagEnum.EFFECT.code)
                .eq(ImportFileInfo::getParentId, PRJ_PARENT_ID)
                .orderByDesc(ImportFileInfo::getCreatedTime));
        List<ImportFileDirQueryVO> page = BeanUtil.copyToList(importFileInfoPage.getRecords(), ImportFileDirQueryVO.class);
        UserThreadLocal.setSuccessInfo("", "", "查询导入本地工程成功");
        return new RespPageVO<>(importFileInfoPage.getTotal(), page);
    }

    @Override
    public RespPageVO<ImportFileInfoVO> queryImportFileInfo(String id, Integer pageNo, Integer pageSize) {
        Page<ImportFileInfo> importFilePage = this.baseMapper.selectPage(new Page<>(pageNo, pageSize), Wrappers.<ImportFileInfo>lambdaQuery()
                .eq(ImportFileInfo::getParentId, id)
                .eq(ImportFileInfo::getDataType, EleDataTypeEnum.FILE.code)
                .eq(ImportFileInfo::getEffectFlag, EffectFlagEnum.EFFECT.code));
        List<ImportFileInfoVO> files = BeanUtil.copyToList(importFilePage.getRecords(), ImportFileInfoVO.class);
        RespPageVO<ImportFileInfoVO> respPageVO = new RespPageVO<>(importFilePage.getTotal(), files);
        UserThreadLocal.setSuccessInfo(id, "", "查询导入本地工程文件夹下的文件和文件夹成功");
        return respPageVO;
    }

    @Override
    @Transactional
    public boolean removePrj(String prjId) {
        LambdaQueryWrapper<ImportFileInfo> idSelectWrapper = Wrappers.lambdaQuery(ImportFileInfo.class)
                .likeRight(ImportFileInfo::getFilePath, prjId + MYSQL_FILE_PATH_SPLIT)
                .select(ImportFileInfo::getId);
        Set<String> ids = this.baseMapper.selectList(idSelectWrapper).stream().map(ImportFileInfo::getId).collect(Collectors.toSet());
        ids.add(prjId);
        this.baseMapper.update(new ImportFileInfo(), Wrappers.<ImportFileInfo>lambdaUpdate()
                .set(ImportFileInfo::getEffectFlag, EffectFlagEnum.NOT_EFFECTIVE.code)
                .in(ImportFileInfo::getId, ids));
        UserThreadLocal.setSuccessInfo(prjId, prjId, "删除导入本地工程成功，工程id为 {}", prjId);
        return true;
    }

    @Override
    public Tree<String> tree(String prdId) {
        List<ImportFileInfo> importFileInfos = this.baseMapper.selectList(Wrappers.<ImportFileInfo>lambdaQuery()
                .eq(ImportFileInfo::getEffectFlag, EffectFlagEnum.EFFECT.code)
                .eq(ImportFileInfo::getDataType, EleDataTypeEnum.FOLDER.code)
                .likeRight(ImportFileInfo::getFilePath, prdId));
        if (CollUtil.isEmpty(importFileInfos)) {
            return null;
        }
        TreeNodeConfig config = new TreeNodeConfig();
        config.setIdKey(ImportPrjTreeVO.Fields.id);
        config.setParentIdKey(ImportPrjTreeVO.Fields.parentId);
        config.setWeightKey(ImportPrjTreeVO.Fields.sort);
        List<Tree<String>> fileTrees = TreeUtil.build(importFileInfos, PRJ_PARENT_ID, config, ((obj, treeNode) -> {
            treeNode.putExtra(ImportPrjTreeVO.Fields.id, obj.getId());
            treeNode.putExtra(ImportPrjTreeVO.Fields.parentId, obj.getParentId());
            treeNode.putExtra(ImportPrjTreeVO.Fields.sort, obj.getSort());
            treeNode.putExtra(ImportPrjTreeVO.Fields.fileName, obj.getFileName());
        }));
        UserThreadLocal.setSuccessInfo(prdId, prdId, "查询导入工程对应的树成功");
        return fileTrees.get(0);
    }

    private void resetPrjName(ImportFileInfo importFileInfo) {
        if (!StrUtil.equals(importFileInfo.getParentId(), ElectromagneticConstants.PRJ_PARENT_ID)) {
            return;
        }
        importFileInfo.setFileType("zip");
        String fileName = importFileInfo.getFileName();
        for (int i = 0; i < 1000; ++i) {
            long count = this.baseMapper.selectCount(Wrappers.lambdaQuery(ImportFileInfo.class)
                    .eq(ImportFileInfo::getParentId, ElectromagneticConstants.PRJ_PARENT_ID)
                    .eq(ImportFileInfo::getFileName, fileName)
                    .eq(ImportFileInfo::getEffectFlag, EffectFlagEnum.EFFECT.code));
            if (count > 0) {
                fileName = fileName + APPEND_NEW_FILE_NAME;
            } else {
                importFileInfo.setFileName(fileName);
                return;
            }
        }
    }

    private String findIdPath(String parentId, Map<String, ImportFileInfo> itemIdMap) {
        List<String> ids = new ArrayList<>();
        while (itemIdMap.containsKey(parentId)) {
            ImportFileInfo zipItem = itemIdMap.get(parentId);
            ids.add(zipItem.getId());
            parentId = zipItem.getParentId();
        }
        CollUtil.reverse(ids);
        return CollUtil.join(ids, "_");
    }

    private Integer createFolderSort(String parentId, Map<String, ImportFileInfo> itemIdMap) {
        long count = itemIdMap.values().stream().filter(e -> e.getParentId().equals(parentId) && e.getDataType().equals(EleDataTypeEnum.FOLDER.code)).count();
        return Math.toIntExact(++count);
    }
}
